import Cesium, { EllipsoidGeodesic, EventHelper, Scene } from '../../../Ces/Cesium';
import loadView from '../Core/loadView.js';

let geodesic: EllipsoidGeodesic;

class DistanceLegendViewModel {
    // 核心数据
    terria: any;
    _removeSubscription: (() => void) | undefined;
    _lastLegendUpdate: number | undefined;
    eventHelper: EventHelper;

    // 显示相关
    distanceLabel: string | undefined;
    barWidth: number | undefined;

    // 配置选项
    enableDistanceLegend: boolean;

    constructor(options: {
        terria: any;
        enableDistanceLegend?: boolean;
        container?: any;
    }) {
        geodesic = new Cesium.EllipsoidGeodesic();
        if (!Cesium.defined(options) || !Cesium.defined(options.terria)) {
            throw new Cesium.DeveloperError('options.terria is required.');
        }

        this.terria = options.terria;
        this._removeSubscription = undefined;
        this._lastLegendUpdate = undefined;
        this.eventHelper = new Cesium.EventHelper();

        this.distanceLabel = undefined;
        this.barWidth = undefined;

        this.enableDistanceLegend = (Cesium.defined(options.enableDistanceLegend)) ? options.enableDistanceLegend : true;

        Cesium.knockout.track(this, ['distanceLabel', 'barWidth']);

        this.eventHelper.add(this.terria.afterWidgetChanged, () => {
            if (Cesium.defined(this._removeSubscription)) {
                this._removeSubscription();
                this._removeSubscription = undefined;
            }
        }, this);

        const that = this;

        function addUpdateSubscription() {
            if (Cesium.defined(that.terria)) {
                const scene = that.terria.scene;

                that._removeSubscription = scene.postRender.addEventListener(() => {
                    updateDistanceLegendCesium(that, scene);
                }, that);
            }
        }

        addUpdateSubscription();
        this.eventHelper.add(this.terria.afterWidgetChanged, () => {
            addUpdateSubscription();
        }, this);
    }

    destroy() {
        this.eventHelper.removeAll();
    }

    show(container: any) {
        let testing: string;

        if (this.enableDistanceLegend) {
            testing = '<div class="distance-legend" data-bind="visible: distanceLabel && barWidth">'
                + '<div class="distance-legend-label" data-bind="text: distanceLabel"></div>'
                + '<div class="distance-legend-scale-bar" data-bind="style: { width: barWidth + \'px\', left: (5 + (125 - barWidth) / 2) + \'px\' }"></div>'
                + '</div>';
        } else {
            testing = '<div class="distance-legend"  style="display: none;" data-bind="visible: distanceLabel && barWidth">'
                + '<div class="distance-legend-label"  data-bind="text: distanceLabel"></div>'
                + '<div class="distance-legend-scale-bar"  data-bind="style: { width: barWidth + \'px\', left: (5 + (125 - barWidth) / 2) + \'px\' }"></div>'
                + '</div>';
        }
        loadView(testing, container, this);
    }

    static create(options: {
        terria: any;
        enableDistanceLegend?: boolean;
        container?: any;
    }) {
        const result = new DistanceLegendViewModel(options);
        result.show(options.container);
        return result;
    }
}

const distances: number[] = [
    1, 2, 3, 5,
    10, 20, 30, 50,
    100, 200, 300, 500,
    1000, 2000, 3000, 5000,
    10000, 20000, 30000, 50000,
    100000, 200000, 300000, 500000,
    1000000, 2000000, 3000000, 5000000,
    10000000, 20000000, 30000000, 50000000
];

function updateDistanceLegendCesium(viewModel: DistanceLegendViewModel, scene: Scene) {
    if (!viewModel.enableDistanceLegend) {
        viewModel.barWidth = undefined;
        viewModel.distanceLabel = undefined;
        return;
    }
    const now = Cesium.getTimestamp();

    if (now < (viewModel._lastLegendUpdate || 0) + 250) {
        return;
    }

    viewModel._lastLegendUpdate = now;

    // Find the distance between two pixels at the bottom center of the screen.
    const width = scene.canvas.clientWidth;
    const height = scene.canvas.clientHeight;

    const left = scene.camera.getPickRay(new Cesium.Cartesian2((width / 2) | 0, height - 1));
    const right = scene.camera.getPickRay(new Cesium.Cartesian2(1 + (width / 2) | 0, height - 1));

    const globe = scene.globe;
    const leftPosition = globe.pick(left, scene);
    const rightPosition = globe.pick(right, scene);

    if (!Cesium.defined(leftPosition) || !Cesium.defined(rightPosition)) {
        viewModel.barWidth = undefined;
        viewModel.distanceLabel = undefined;
        return;
    }

    const leftCartographic = globe.ellipsoid.cartesianToCartographic(leftPosition);
    const rightCartographic = globe.ellipsoid.cartesianToCartographic(rightPosition);

    geodesic.setEndPoints(leftCartographic, rightCartographic);
    const pixelDistance = geodesic.surfaceDistance;

    // Find the first distance that makes the scale bar less than 100 pixels.
    const maxBarWidth = 100;
    let distance: number | undefined;

    for (let i = distances.length - 1; !Cesium.defined(distance) && i >= 0; --i) {
        if (distances[i] / pixelDistance < maxBarWidth) {
            distance = distances[i];
        }
    }

    if (Cesium.defined(distance)) {
        let label: string;

        if (distance >= 1000) {
            label = (distance / 1000).toString() + ' km';
        } else {
            label = distance.toString() + ' m';
        }

        viewModel.barWidth = (distance / pixelDistance) | 0;
        viewModel.distanceLabel = label;
    } else {
        viewModel.barWidth = undefined;
        viewModel.distanceLabel = undefined;
    }
}

export default DistanceLegendViewModel;
